
class GeneralStim():
    '''
    A general stim class which has a teardown function so we don't have 
        problems with pyglet holding on to memory forever.
    '''
    def __init__():
        self.textures = []

    def teardown(self):
        for texture in self.textures:
            texture.clearTextures()
    

def setup_window(window, win_size, eye_cup, rf, save_frames, save_frames_factor, 
                 save_frames_size, shade):
    '''
    This function determines if a new window needs to be made and returned,
    based on the arguements passed.
    Inputs:
        window      : Either a psychopy.visual window or None. If none, a new window
                        will be constructed.
        win_size    : If window is none, then this will be the size of the new
                        window, unless save_frames is True then the size will be
                        determined by save_frames_size instead.
        eye_cup     : Either an Eye_cup object or None. Used to setup the window
                        only in the case that rf is None and save_frames is False.
        rf          : Either an RF object or None. Used to setup the window if
                        save_frames is False.
        save_frames : If True, then a new window will be constructed, even if one
                        was passed in, and it will be of size save_frames_size.
        save_frames_factor  : How many rf units should be in the save_frames window?
        save_frames_size : This will determine the size of the window in save_frames
                            is true.
        shade        : The background shade of a newly created window, if created.
    Returns:
        return_list  : A list containing at least a new window and possibly a new
                       eye_cup and or rf.
                        [window] -or- [window, rf] -or- [window, rf, eye_cup]
    '''
    # simplest case, just return the window in a one element list.
    if (not save_frames) and (window is not None):
        return [window]

    from psychopy import visual
    from Eye_cup import Eye_cup
    from RF import RF
    import copy
    import os

    # see if the xscreen is capable of displaying the stimulus full sized.
    resolution_line = os.popen("xdpyinfo | grep dimensions")
    tokens = resolution_line.readline().split('x')
    height = int(tokens[1].split()[0])
    # if height is 1800, then it can be fully displayed, if it is 900 we need
    # a much smaller window, placed in the upper right corner.

    # if we are NOT capturing frames, just construct a window.
    if (not save_frames) and (window is None):
        rf_was_created      = False
        eye_cup_was_created = False
        if rf is None: # rf needs to be constructed
            # do we need to create an eye_cup?
            if eye_cup is None:
                print "No eye cup object passed, reading eye coordinates from file."
                eye_cup = Eye_cup.from_eye_cup_file()
                eye_cup_was_created = True
            rf = RF( [copy.deepcopy(eye_cup.cup[0]), copy.deepcopy(eye_cup.cup[1])],
                     [copy.deepcopy(eye_cup.cup[2]), copy.deepcopy(eye_cup.cup[2])], 
                     0.0 )
            rf_was_created = True

        # now we have an rf object, so just return the window and anything we made.
        if height > 900:
            view_scale = ( 2.0*rf.size[0]/float(win_size[0]),
                           2.0*rf.size[1]/float(win_size[1]) ) 
            view_pos   = ( 2.0*rf.center[0]/win_size[0],
                           2.0*rf.center[1]/win_size[1] ) 
            pos = (0,-900)
        else:
            shrink_factor = 5.0
            view_scale = ( 2.0*rf.size[0]/float(win_size[0]),
                           2.0*rf.size[1]/float(win_size[1])) 
            view_pos   = ( 2.0*rf.center[0]/win_size[0],
                           2.0*rf.center[1]/win_size[1] ) 
            win_size = (1400/int(shrink_factor),900/int(shrink_factor))
            pos = (1440-win_size[0]-10,125)
        view_ori   = rf.ori
        myWin = visual.Window( win_size, 
                               allowGUI=False, 
                               fullscr=False, 
                               pos=pos, 
                               rgb=1.0*shade,
                               viewScale = view_scale,
                               viewPos   = view_pos,
                               viewOri   = view_ori  )
        return_list = [myWin]
        if rf_was_created:      return_list.append(rf)
        if eye_cup_was_created: return_list.append(eye_cup)
        return return_list
    else: # we need to set up a frame capturing window.
        myWin = visual.Window( (save_frames_size, save_frames_size), 
                               allowGUI=False, 
                               fullscr=False, 
                               pos=(0,-900), 
                               viewScale = 2.0*(1.0/save_frames_factor),

                               rgb=1.0*shade)
        return [myWin]
            

def general_stimulus( stim_list, shade, win_size,
                         window=None,
                         eye_cup=None, 
                         rf=None, 
                         save_frames=False,
                         save_frames_factor=1.0,
                         save_frames_size=100,
                         display_saved_frames=False,
                         preview=False):
    """
    This is a general stimulus, the stimulus is fully described by the
        stim objects in the stim_list, they provide .draw() and .evolve() methods.
    Inputs: 
        stim_list       : a list of stim objects capable of .draw() and .evolve()
        shade           : the background shade of the display window
        win_size        : the size of the display window (ignored if save_frames)
        --- kwargs --
        window          : a psychopy.visual Window object, if not passed it will 
                             be constructed. If passed it will not be closed
        eye_cup         : An Eye_cup object so we can draw the eye_cup on the 
                             screen and set up the normalized coordinate system
                             in the case no rf object is passed.
        rf              : an RF object so we can set up the normalized coordinate 
                             system
        save_frames     : If True, function will return the frames captured.
        save_frames_factor  : How large a view area around the rf should be saved?
                                1.0 would be exactly the RF size
                                2.0 would save an additional 0.5 RF units on all 
                                    sides
        save_frames_size    : How many pixels should the saved frames be? This will
                                mean the saved frames are nxn where n is this value.
        display_saved_frames    : Should we display the frames as we render them?
                                    It will run much faster if we don't have to.
        preview         : if True then the frames will display immediately,
                             otherwise we will wait for a pulse on the parallel port.
    Returns:
        times           : The times when frames were shown, with zero being the first frame time.
        saved_frames    : optionally will be returned if save_frames is True.
    """
    from psychopy import core, event, visual
    # -----------------------------------------
    # -- Set up the window to display things --
    # -----------------------------------------
    win_list = setup_window( window, win_size, eye_cup, rf,
                             save_frames, save_frames_factor,
                             save_frames_size, shade )
    win_list.reverse()
    myWin = win_list.pop()

    # setup the stimulus objects now that we have a window.
    for stim in stim_list:
        stim.setup(myWin)

    # set up this rf and this eye_cup (trf, teye_cup)
    trf = rf
    teye_cup = eye_cup
    try: trf = win_list.pop()
    except: pass
    try: teye_cup = win_list.pop()
    except: pass
    
    if save_frames: # don't try to setup or draw the eye_cup or rf
        trf = teye_cup = None # if we're saving frames

    if trf is not None:
        trf.prep_for_drawing(myWin, shade=shade, reverse_rf=True)
    if teye_cup is not None:
        teye_cup.prep_for_drawing(myWin, shade=shade, rf=trf)

    # -----------------------
    # --  WAIT FOR A PULSE --
    # -----------------------
    if not preview:
        import parallel
        from pport_functions import wait_for_pulse
        p = parallel.Parallel()     # get access to the parallel port
        print "Waiting for pulse..."
        wait_for_pulse(p) # poll the parallel port for a pulse every pport_dt seconds
    else:
        print "Not Waiting for pulse..."

    # start keeping track of time
    trialClock = core.Clock()       # start the clock
    times = [0.0]                   # time begins at 0.0

    keep_looping = True
    while keep_looping:
        if teye_cup is not None:
            teye_cup.draw()
        if trf is not None:
            trf.draw()

        # draw the stimuli
        for stim in stim_list:
            stim.draw()
            stim.evolve() # get stim ready for next drawing.

        for stim in stim_list:
            if stim.exhausted:
                keep_looping = False
    
        if save_frames:
            myWin.getMovieFrame(buffer='back')
            if display_saved_frames:
                myWin.flip()
            else:
                visual.GL.glClear( visual.GL.GL_COLOR_BUFFER_BIT | 
                                   visual.GL.GL_DEPTH_BUFFER_BIT  )
                myWin._defDepth=0.0 # has to be reset each frame
        else:
            myWin.flip()
                
        # keep track of when buffers were flipped
        times.append( trialClock.getTime() )

        #handle key presses each frame
        for key in event.getKeys():
            if key in ['escape','q']:
                keep_looping = False

    if window == None: # no window object was passed.
        myWin.close()

    # tear down the stimulus objects
    for stim in stim_list:
        stim.teardown()

    if save_frames:
        return times, myWin.movieFrames
    else:
        return times
            
        
            
    
    

    
    
